module top_systolic #(// no expshare
    parameter ARR_Len = 16  ,
    parameter EXP_Wid = 3  ,
    parameter MSA_Wid = 3  ,
    parameter OUT_Wid = 2 * (2 ** EXP_Wid - 1 + MSA_Wid),
    parameter SUM_Wid = 20
)(
    input                                           clk     ,

	input		[ARR_Len*(EXP_Wid+MSA_Wid+1)-1:0] 	in_a	,
	input		[ARR_Len*(EXP_Wid+MSA_Wid+1)-1:0] 	in_b	,

    input                                           out_en_i,
    output                                          out_en_o,
	output      [ARR_Len*(SUM_Wid          )-1:0] 	out_result		
);

wire [EXP_Wid+MSA_Wid:0] 	a	        [0:ARR_Len][0:ARR_Len]  ;	
wire [EXP_Wid+MSA_Wid:0] 	b	        [0:ARR_Len][0:ARR_Len]  ;	
wire [SUM_Wid-1:0] 	        result      [0:ARR_Len][0:ARR_Len]  ;

reg                         oen         [0:ARR_Len]             ;
always @(posedge clk) oen[0]       <= out_en_i                  ;
assign                out_en_o      = oen[ARR_Len-1]            ;

genvar i,j;
generate 
    for (i = 0; i < ARR_Len; i = i + 1) begin:wire_assign
        assign a[0][i]      = in_a[(i+1)*(EXP_Wid+MSA_Wid+1)-1:i*(EXP_Wid+MSA_Wid+1)];
        assign b[i][0]      = in_b[(i+1)*(EXP_Wid+MSA_Wid+1)-1:i*(EXP_Wid+MSA_Wid+1)];
        assign result[0][i] = {SUM_Wid{1'b0}};
        assign out_result[(i+1)*(SUM_Wid)-1:i*(SUM_Wid)] = result[ARR_Len][i];

        always @(posedge clk) begin
            oen[i+1]        <= oen[i];
        end
    end

    for (i = 0; i < ARR_Len; i = i + 1) begin:col
        for (j = 0; j < ARR_Len; j = j + 1) begin:row
            systolic #(
                .EXP_Wid    (EXP_Wid                ),
                .MSA_Wid    (MSA_Wid                ),
                .OUT_Wid    (OUT_Wid                ),
                .SUM_Wid    (SUM_Wid                )
            )u_systolic(
                .clk        (clk                    ), 

                .a          (a[i][j]                ),
                .b          (b[i][j]                ),
                .result	    (result[i+1][j]         ),

                .out_en     (oen[j]                 ),
                .a_out	    (a[i+1][j]              ),
                .b_out	    (b[i][j+1]              ),
                .result_in  (result[i][j]           )
            );
        end
    end
endgenerate
endmodule

module top_systolic_expshare #(// expshare 1
    parameter ARR_Len = 16  ,
    parameter EXP_Wid = 3  ,
    parameter MSA_Wid = 3  ,
    parameter Eshare_Wid = 3,
    parameter OUT_Wid = 2 * (2 ** EXP_Wid - 1 + MSA_Wid + 2 ** Eshare_Wid - 1),
    parameter SUM_Wid = 34,
    parameter Fra_Wid = 0
)(
    input                                           clk     ,

	input		[ARR_Len*(EXP_Wid+MSA_Wid+1)-1:0] 	in_a	,
	input		[ARR_Len*(EXP_Wid+MSA_Wid+1)-1:0] 	in_b	,
	input		[Eshare_Wid-1:0] 	                exp_share_a,
	input		[Eshare_Wid-1:0] 	                exp_share_b,

    input                                           out_en_i,
    output                                          out_en_o,
	output      [ARR_Len*(SUM_Wid-Fra_Wid  )-1:0] 	out_result		
);

wire [EXP_Wid+MSA_Wid:0] 	a	        [0:ARR_Len][0:ARR_Len]  ;	
wire [EXP_Wid+MSA_Wid:0] 	b	        [0:ARR_Len][0:ARR_Len]  ;	
wire [SUM_Wid-Fra_Wid-1:0] 	result      [0:ARR_Len][0:ARR_Len]  ;

reg                         oen         [0:ARR_Len]             ;
always @(posedge clk) oen[0]       <= out_en_i                  ;
assign                out_en_o      = oen[ARR_Len-1]            ;

reg  [Eshare_Wid:0]         exp_share   [0:2*ARR_Len]           ;
always @(posedge clk) exp_share[0] <= exp_share_a + exp_share_b ;

genvar i,j;
generate 
    for (i = 0; i < ARR_Len; i = i + 1) begin:wire_assign
        assign a[0][i]      = in_a[(i+1)*(EXP_Wid+MSA_Wid+1)-1:i*(EXP_Wid+MSA_Wid+1)];
        assign b[i][0]      = in_b[(i+1)*(EXP_Wid+MSA_Wid+1)-1:i*(EXP_Wid+MSA_Wid+1)];
        assign result[0][i] = {(SUM_Wid-Fra_Wid){1'b0}};
        assign out_result[(i+1)*(SUM_Wid-Fra_Wid)-1:i*(SUM_Wid-Fra_Wid)] = result[ARR_Len][i];

        always @(posedge clk) begin
            exp_share[2*i+2]<= exp_share[2*i+1];
            exp_share[2*i+1]<= exp_share[2*i  ];
            oen[i+1]        <= oen[i];
        end
    end

    for (i = 0; i < ARR_Len; i = i + 1) begin:col
        for (j = 0; j < ARR_Len; j = j + 1) begin:row
            systolic_expshare #(
                .EXP_Wid    (EXP_Wid                ),
                .MSA_Wid    (MSA_Wid                ),
                .Eshare_Wid (Eshare_Wid             ),
                .OUT_Wid    (OUT_Wid                ),
                .SUM_Wid    (SUM_Wid                ),
                .Fra_Wid    (Fra_Wid                )
            )u_systolic(
                .clk        (clk                    ), 

                .a          (a[i][j]                ),
                .b          (b[i][j]                ),
                .result	    (result[i+1][j]         ),
                .exp_share  (exp_share[i+j]         ),

                .out_en     (oen[j]                 ),
                .a_out	    (a[i+1][j]              ),
                .b_out	    (b[i][j+1]              ),
                .result_in  (result[i][j]           )
            );
        end
    end
endgenerate
endmodule
